---
title: 'Blur Loader with Cloudinary'
tags: 'nextjs'
description: "Achieve 'Gatsby-like' Blur Loader using Cloudinary, and Next Image."
---

> Blur loader I used throughout this website.

Inspired by [Colby Fayock Blog Post](https://spacejelly.dev/posts/how-to-use-cloudinary-images-in-next-js-with-blurred-placeholders/).

## CloudinaryImg Component

```jsx
import { buildUrl } from 'cloudinary-build-url';
import clsx from 'clsx';
import Image from 'next/image';
import * as React from 'react';
import Lightbox from 'react-image-lightbox';

import 'react-image-lightbox/style.css';

type CloudinaryImgType = {
  publicId: string,
  height: string | number,
  width: string | number,
  alt: string,
  title?: string,
  className?: string,
  preview?: boolean,
  noStyle?: boolean,
  aspect?: {
    width: number,
    height: number,
  },
  mdx?: boolean,
} & React.ComponentPropsWithoutRef<'figure'>;

export default function CloudinaryImg({
  publicId,
  height,
  width,
  alt,
  title,
  className,
  preview = true,
  noStyle = false,
  mdx = false,
  style,
  aspect,
  ...rest
}: CloudinaryImgType) {
  const [isOpen, setIsOpen] = React.useState < boolean > false;

  const urlBlurred = buildUrl(publicId, {
    cloud: {
      cloudName: 'theodorusclarence',
    },
    transformations: {
      effect: {
        name: 'blur:1000',
      },
      quality: 1,
      rawTransformation: aspect
        ? `c_fill,ar_${aspect.width}:${aspect.height},w_${width}`
        : undefined,
    },
  });
  const url = buildUrl(publicId, {
    cloud: {
      cloudName: 'theodorusclarence',
    },
    transformations: {
      rawTransformation: aspect
        ? `c_fill,ar_${aspect.width}:${aspect.height},w_${width}`
        : undefined,
    },
  });

  const aspectRatio = aspect ? aspect.height / aspect.width : undefined;

  return (
    <figure
      className={clsx(className, {
        'overflow-hidden rounded shadow-sm dark:shadow-none': !noStyle,
        'mx-auto': mdx && width <= 800,
      })}
      style={{
        ...(mdx && width <= 800 ? { maxWidth: width } : {}),
        ...style,
      }}
      {...rest}
    >
      <div
        style={{
          position: 'relative',
          height: 0,
          paddingTop: aspectRatio
            ? `${aspectRatio * 100}%`
            : `${(+height / +width) * 100}%`,
          cursor: preview ? 'zoom-in' : 'default',
        }}
        className='img-blur'
        onClick={preview ? () => setIsOpen(true) : undefined}
      >
        <style jsx>{`
          .img-blur::before {
            content: '';
            position: absolute;
            inset: 0;
            filter: blur(20px);
            z-index: 0;
            background-image: url(${urlBlurred});
            background-position: center center;
            background-size: 100%;
          }
        `}</style>
        <div className='absolute left-0 top-0'>
          <Image
            width={width}
            height={height}
            src={url}
            alt={alt}
            title={title || alt}
          />
        </div>
      </div>
      {isOpen && (
        <Lightbox mainSrc={url} onCloseRequest={() => setIsOpen(false)} />
      )}
    </figure>
  );
}
```

## Usage

### 1. For full width

```jsx
<figure className='overflow-hidden rounded-sm shadow-md dark:shadow-none'>
  <CloudinaryImg
    publicId='theodorusclarence/cloudinaryId.jpg'
    width='1440'
    height='792'
    alt='Your alt text'
  />
</figure>
```

### 2. For specified width and centered

If not using jit or in mdx, width can be replaced by using inline-style

```jsx
<figure className='mx-auto w-[210px] shadow-md dark:shadow-none'>
  <CloudinaryImg
    publicId='theodorusclarence/cloudinaryId.png'
    width='210'
    height='149'
    alt='Your alt text'
  />
</figure>
```
